//
//  PeerConnectionsController.m
//  SolarSeek
//
//  Created by Erick Fejta on 05/02/10.
//  Copyright 2005 Erick Fejta. All rights reserved.
//

#import "PeerConnectionsController.h"
#import "SelfFillingMutables.h"
#import "Connections.h"
#import "ConnectionsIdentifier.h"
#import "TimelyInformation.h"
#import "PeersController.h"
#import "Message.h"
#import "Peers.h"
#import "EnumTypes.h"
#import "SolarSeekEngine.h"
#import "ServerConnectionController.h"


#pragma mark -

#ifdef _SOLARSEEK_NEW_


@interface ConnectionsController (Private)
- (void)requestAddressForPeer:(Peer *)peer;
- (void)requestPortForPeer:(Peer *)peer;
- (NSString *)addressForPeer:(Peer *)peer;
- (UInt16)portForPeer:(Peer *)peer;
@end

#pragma mark -


@implementation PeerConnectionsController

- (id)init {
	[NSException raise:@"InvalidArgumentException" format:@"[PeersController init] is not a valid initializer."];
	[self release];
	return nil;
}


- (id)initWithEngine:(SolarSeekEngine *)engine {
	if(self = [super init]) {
		_engine = engine;
		_handlers = [[NSMutableDictionary alloc] init];
		_connections = [[NSMutableDictionary alloc] init];
		_messageNewServer = nil;
		_messageNewPeer = nil;
	}
	return self;
}

- (void)dealloc {
	[self remoteAllConnections];
	[self removeAllHandlers];
	[_handlers release];
	_handlers = nil;
	[_connections release];
	_connections = nil;
	[_messageNewServer release];
	[_messageNewPeer release];
	[super dealloc];
}

- (void)connectHandlerFirst:(ConnectionHandler *)handler {
	[handler setState:HandlerConnectionFirst description:@"Deciding first attempt strategy..."];
	
	if([handler isAnswerable]) {
		[self requestConnectForHandler:handler];
	}
	else if([handler isCallable]) {
		[self connectForHandler:handler];
	}
	else {
		if([handler containsNeed:@"DMZ"]) {
			[handler setCallable:NO];
			[self failConnect:handler reason:@"Firewall likely preventing connection.  Configure firewall's DMZ"];
		}
		else {
			[handler addNeed:@"DMZ"];
			[self failConnect:handler reason:@"Firewall likely preventing connection."];
		}
	}
}

- (void)connectHandlerSecond:(ConnectionHandler *)handler {
	[handler setState:HandlerConnectionFirst description:@"Deciding second attempt strategy..."];

	if([handler isAnswerable]) {
		[self connectForHandler:handler];
	}
	else {
		[handler addNeed:@"DMZ"];
		[self failConnect:handler reason:@"Cannot connect.  Both parties may be firewalled."];
	}
}


- (PeerConnectionHandler *)connectToPeer:(Peer *)peer {
	PeerConnectionHandler *handler = [self handlerForPeer:peer];
	[handler connectHandlerFirst:handler];
	return handler;
}
		
- (TransferConnectionHandler *)connectToTransfer:(Transfer *)transfer {
	TransferConnectionHandler *handler = [self handlerForTransfer:transfer];
	[handler connectHandlerFirst:handler];
	return handler;
}

- (void)failConnect:(ConnectionHandler *)connectionHandler reason:(NSString *)reason {
	reason = reason?@"Cannot establishConnection";
	switch([connectionHandler state]) {
		case HandlerConnectFirst: {
			[self connectSecond:connectionHandler];
			break;
		}
		default:
			[self setState:HandlerConnectionFailed description:reason];
	}
}
	

- (void)failRequestAddressForHandler:(ConnectionHandler *)connectionHandler {
	if([connectionHandler containsNeed:@"Address"]) {
		[self failConnect:connectionHandler];
	}
}

- (void)requestAddressForHandler:(ConnectionHandler *)connectionHandler {
	Message *m = [Message message];
	[m putCode:GetPeerAddress];
	[m putString:[[connectionHandler peer] name]];
	[[NSApp engine] sendServerMessage:m];
	[self performSelector:@selector(failRequestAddressForHandler:) withObject:handler afterDelay:60.0];
}

- (void)requestPortForHandler:(ConnectionHandler *)handler { [self requestAddressForHandler:handler]; }

- (NSString *)addressForHandler:(ConnectionHandler *)handler { 
	return [_addresses objectForKey:[NSString stringWithFormat:@"address.%@",[[handler peer] name]]];
}
- (UInt16)portForPeer:(Peer *)peer { 
	NSNumber *n = [_addresses objectForKey:[NSString stringWithFormat:@"port.%@",[[handler peer] name]]];
	return n?[n intValue]:0;
}

- (void)attachHandler:(ConnectionHandler *)handler toConnection:(NewConnetion *)connection {
	NSString *key = [NSString stringWithFormat:@"connection.%d",handler];
	[_handlers setObject:connection forKey:key];
	[connection setHandler:handler];
	[_connections addObject:connection];
	[handler setState:HandlerConnected description:@"Connected"];	
}

- (NewConnection *)connectionForHandler:(ConnectionHandler *)handler {
	NSString *key = [NSString stringWithFormat:@"connection.%d",handler];
	return [_handlers objectForKey:key];
}


- (void)requestConnectForHandler:(ConnectionHandler *)handler {
	[handler setDesscription:@"Requesting an incoming connection..."];
	Message *m = [Message messageWithCode:ConnectToPeer];
	unsigned token = [[NSApp engine] tokenNext];
	[m putInt:token];
	[m putString:[[handler peer] name]];
	[m putString:[handler type]];
	if([[[NSApp engine] server] isConnected]) {
		NSNumber *n = [NSNumber numberWithUnsignedInt:token];
		[_handlers setObject:handler forKey:n];
		[[NSApp engine] sendServerMessage:m];
		[self scheduleRemoveHandlerForKey:n afterDelay:60.0];
	}
	else {
		[self failConnect:handler reason:@"Cannot request connections with disconnected from server"];
	}
}

- (void)connectForHandler:(ConnectionHandler *)handler {
	[handler setDescription:@"Trying to connect directly..."];
	NSString *address = [self addressForHandler:handler];
	if(address == nil || [address isEqual:@""]) {
		[handler addNeed:@"Address"];
		[handler setDescription:@"Asking for address..."];
		[self requestAddressForHandler:handler];
		return;
	}
	UInt16 port = [self portForHandler:handler];
	if(port == 0 || port == NSNotFound) {
		[handler addNeed:@"Port"];
		[handler setDescription:@"Asking for port..."];
		[self requestPortForHandler:handler];
		return;
	}
	NewConnection *conn = [[NewConnection alloc] init];
	if([conn connectToHost:address onPort:port]==YES) {	//we are connecting.
		[handler setDescription:@"Connecting to %@:%d...",address,port];
		[conn addObserver:self forKeyPath:@"isConnected" options:nil context:handler];
	}
	else {
		[self failConnect:connectionHandler];
		[conn release];
	}
}
	

- (PeerConnectionHandler *)handlerForPeer:(Peer *)peer {
	PeerConnectionHandler *pch = [_handlers objectForKey:peer];
	if(pch == nil) {
		PeerConnectionHandler *pch = [[PeerConnectionHandler alloc] initWithPeer:peer];
		[_handlers setObject:pch forKey:peer];
		[pch release];
	}
	return pch;
}

- (TransferConnectionHandler *)handlerForTransfer:(Transfer *)transfer {
	TransferConnectionHandler *tch = [_handlers objectForKey:transfer];
	if(tch == nil) {
		TransferConnectionHandler *tch = [[TransferConnectionHandler alloc] initWithTransfer:transfer];
		[_handlers setObject:tch forKey:transfer];
		[tch release];
	}
	return tch;
}

- (void)queueMessage:(Message *)message {
	NSString *key = [NSString stringWithFormat:@"queue.%@",[[message peer] name]];
	NSMutableArray *array = [_handler objectForKey:key];
	if(array == nil) {
		array = [[NSMutableArray alloc] init];
		[_handler setObject:array forKey:ke];
		[array release];
		[self cancelRemoveHandlerForKey:key remove:NO];
		[self scheduleRemoveHandlerForKey:key];
	}
	[array addObject:message];
}
	

- (BOOL)sendPeerMessage:(Message *)message {
	Peer *peer = [message peer];
	PeerConnectionHandler *pch = [self handlerForPeer:peer];
	if([pch isConnected]) {
		NewConnection *nc = [self connectionForHandler:pch];
		[nc sendMessage:m];
		return YES;
	}
	else {
		switch([pch state]) {
			case HandlerConnectFirst:
			case HandlerConnectSecond:
				[self queuePeerMessage:message];
				return YES;
				break;
			default:
				[self connectToPeer:peer];
				if([pch state] == HandlerConnectionFailed) {
					return NO;
				}
				else {
					[self queuePeerMessage:message];
					return YES;
				}
		}
	}
}
	

- (BOOL)sendMessage:(Message *)message toPeer:(Peer *)peer {
	[message setPeer:peer];
	return [self sendPeerMessage:message];
}

- (BOOL)sendMessageToServer:(Message *)message {
	[self adf];
}
	

- (Message *)messageNewServer { return _messageNewServer; }	//kv observable.
- (Message *)messageNewPeer { return _messageNewPeer; }	//kv observable.
- (Peer *)peerWithMessageNew { return [_messageNewPeer peer]; }	//kv observable

- (void)stopIdentifyingTransferWithConnection:(NewConnection *)connection peer:(Peer *)peer {
	[connection removeObserver:self forKeyPath:@"isConnected"];
	[connection removeObserver:self forKeyPaty:@"countBytesAvailable"];
}

- (void)scheduleRemoveHandlerForKey:(id)key afterDelay:(NSTimeInterval)seconds {
	[_handlers performSelector:@selector(removeObjectForKey:) withObject:key afterDelay:seconds];
}

- (void)cancelRemoveHandlerForKey:(id)key remove:(BOOL)remove {
	if(remove)
		[_handlers removeObjectForKey:key];
	[NSObject cancelPreviousPerformRequestsWithTarget:_handlers selector:@selector(removeObjectForKey:) object:key];
}

- (void)allowTransfer:(Transfer *)transfer toConnectWithToken:(unsigned)token {
	//this will allow a peer to connect to us and send us a transfer with "token"
	TransferConnectionHandler *tch = [self handlerWithTransfer:transfer];
	NSString *key = [NSString stringWithFormat:@"transfers.%@.%d",[[transfer peer] name],token]; 
	[_handlers setObject:tch forKey:key];
	[self scheduleRemoveHandlerForKey:key afterDelay:60.0];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	if([object isKindOfClass:[NewConnection class]]) {
		if([keyPath isEqual:@"isConnected"]) {
			id o = (id)context;
			if([o isKindOfClass:[ConnectionHandler class]]) {
				[self sendInitMessageToHandler:(ConnectionHandler *)context];
				ConnectionHandler *handler = (ConnectionHandler *)context;
				NewConnection *nc = (NewConnection *)object;
				if([nc isConnected]) {
					Message *m = [Message messageWithCode:PeerInit];
					[m putString:[[[NSApp engine] me] name]];
					[m putString:[TypeConnectionPeer]];
					[m putString:100];
					[nc sendMessage:m];
					[self attachHandler:handler toConnection:nc];
				}
				else if([handler state]!=HandlerConnected) 
					[self failConnect:connectionHandler];
				[object removeObserver:self forKeyPath:@"isConnected"];
				[object release];
			}
			else if([o isKindOfClass:[Peer class]]) {
				[self stopIdentifyingTransferWithConnection:nc peer:(Peer *)o];
				[nc release];
				[o release];
			}
		}
		else if([keyPath isEqual:@"countBytesAvailable"]) {
			if([nc countBytesAvailable] >= 4) {
				unsigned int token;
				NSData *d = [nc readData:4];
				[d getBytes:&token length:4];
				token = NSSwapLittleIntToHost(token);
				NSNumber *num = [NSNumber numberWithUnsignedInt:token];
				Peer *p = (Peer *)context;
				NSString *key = [NSString stringWithFormat:@"transfers.%@.%d",[p name],token];
				TransferConnectionHandler *tch = [_handlers objectForKey:key];
				if(tch) {
					[self attachHandler:tch toConnection:nc];
					[self cancelRemoveHandlerForKey:key remove:YES];
				}
				[self stopIdentifyingTransferWithConnection:nc peer:p];
				[nc release];
				[p release];
			}
		}
	}
	else if(object==_connectionsIdentifier && [keyPath isEqual:@"connectionOpened"]) {
		UnidentifiedConnection *uc = [_connectionsIdentifier connectionOpened];
		if([[uc type] isEqual:TypeConnectionPeer]) {
			Peer *p = [[NSApp engine] peerNamed:[uc name]];
			PeerConnectionHanlder *h = [self handlerForPeer:p];
			NewConnection *nc = [[NewConnection alloc] init];
			[nc connectToSocket:[uc socket]];
			[self attachHandler:h toConnection:nc];
			[nc release];
		}
		else if([[uc type] isEqual:TypeConnectionTransfer]) {
			Peer *p = [[NSApp engine] peerNamed:[uc name]];
			NewConnection *nc = [[NewConnection alloc] init];
			[nc connectToSocket:[uc socket]];
			[nc addObserver:self forKeyPath:@"countBytesAvailable" options:nil context:[p retain]];
			[nc addObserver:self forKeyPath:@"isConnected" options:nil context:p];
			//must release p and nc later.
		}
		else if([[uc type] isEqual:TypeConnectionPierceFirewall]) {
			unsigned token = [uc token];
			NSNumber *num = [NSNumber numberWithUnsignedInt:token];
			ConnectionHandler *h = [_handlers objectForKey:num];
			if(h) {
				NewConnection *nc = [[NewConnection alloc] init];
				[nc connectToSocket:[uc socket]];
				[self attachHandler:h toConnection:nc];
				[nc release];
				[self cancelRemoveHandlerForKey:num remove:YES];
			}
		}
	}
}


- (void)updatePeer:(Peer *)peer withFileSearchResult:(Message *)message {
	if([message decompress]) {
		[message getString]; //name
		[message getInt]; //token
		int files = [message getInt];
		while(files--) {
			[message getByte];
			[message getData];
			[message getLongLong];
			[message getString];
			int attributes = [message getInt];
			while(attributes--) {
				[message getInt];
				[message getInt];
			}
		}
		NSNumber *countSlotsFree = [NSNumber numberWithUnsignedChar:[message getByte]];
		NSNumber *speedUpload = [NSNumber numberWithUnsignedInt:[message getInt]];
		NSNumber *countQueued = [NSNumber numberWithUnsignedInt:[message getInt]];
		
		NSDictionary *d = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:countSlotsFree,speedUpload,countQueued,nil]
				forKeys:[NSArray arrayWithObjects:@"countSlotsAvailable",@"speedAverage",@"countDownloadsQueued",nil]];
		[peer setValuesForKeysWithDictionary:d];
	}
	else
		NSLog(@"Could not decompress search results from %@.",peer);
}

- (void)updatePeer:(Peer *)peer withUserInfo:(Message *)message {
	NSString *desc = [message getString];
	id pic = nil;
	if([message getByte] != 0)
		pic = [[[NSImage alloc] initWithData:[message getData]] autorelease];
	else
		pic = [NSNull null];
	unsigned slotstotal = [message getInt],
			 queue = [message getInt],
			 slotsavailable = [message getByte];
	NSDictionary *dic = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:desc,pic,
		[NSNumber numberWithUnsignedInt:slotsavailable],[NSNumber numberWithUnsignedInt:slotstotal],
		[NSNumber numberWithUnsignedInt:queue],nil] 
		forKeys:[NSArray arrayWithObjects:@"comment",@"picture",@"countSlotsAvailable",@"countSlotsTotal",@"countDownloadsQueued",nil]];
	[peer setValuesForKeysWithDictionary:dic];
}
@end

#endif